<?php
abstract class SqlAdapter extends Object
{
    protected $name;
    protected $driver;
    protected $quote;
    protected $transactionId;

    protected function SqlAdapter($name)
    {
        $this->name = $name;
    }

    abstract public function ShowTables();

    abstract public function Describe($table);

    abstract public function GetInsertId();

    abstract public function GetAffectedRows();

    abstract protected function FetchResult($result);

    abstract public function ExecuteMulti($sql);

    abstract public function Select($table_list, $fields = null, $condition = null, $join = null, $orderby = null, $limit = null, &$page = 1, &$count = null, $groupby = null);

    abstract public function GetFields($table);

    public function Execute($sql)
    {
        if (is_object($sql))
            $sql = $this->ToSql($sql);

        try
        {
            if (AppConfig::GetInstance()->IsDebugMode())
            {
                $timer = new Timer();
                $result = $this->driver->query($sql);
                Log::LogSql($sql, $this->GetAffectedRows(), $timer->Stop());
            }
            else
                $result = $this->driver->query($sql);
        }
        catch (Exception $ex)
        {
            $errno = $this->GetLastError();
            Log::LogSqlError($ex->getMessage(), $sql, $errno);
        }

        $errno = $this->GetLastError();
        if ($errno)
            Log::LogSqlError($this->GetErrorMessage(), $sql, $errno);

        return $result;
    }

    public function ExecuteFile($file)
    {
        return $this->ExecuteMulti(FileHandler::Read($file));
    }

    public function Query($sql)
    {
        return $this->FetchResult($this->Execute($sql));
    }

    public function QueryScalar($sql)
    {
        $data = $this->Query($sql);
        return count($data) > 0 ? array_shift($data[0]) : null;
    }

    public function SelectScalar($table, $field, $condition, $join, $orderby = null)
    {
        $data = $this->Select($table, $field, $condition, $join, $orderby, 1);
        return count($data) > 0 ? array_shift($data[0]) : null;
    }

    public function SelectDistinct($table, $field, $orderby = null, $condition = null, $join = null)
    {
        $this->AddQuotes($field);
        return $this->Select($table, "DISTINCT($field)", $condition, $join, $orderby);
    }

    public function Update($table, $update_values, $condition)
    {
        Log::LogQueryType('UPDATE', $table);
        $this->AddQuotes($table);
        $sql = "UPDATE $table SET $update_values WHERE ".$this->ToSql($condition);
        $this->Execute($sql);
        return $this->GetAffectedRows();
    }

    public function Insert($table, $fields, $values)
    {
        Log::LogQueryType('INSERT', $table);
        $this->AddQuotes($table);
        $this->AddQuotes($fields);
        $sql = "INSERT INTO $table ($fields) VALUES ($values)";
        $this->Execute($sql);
        return $this->GetInsertId();
    }

    public function Delete($table, $condition)
    {
        Log::LogQueryType('DELETE', $table);
        $this->AddQuotes($table);
        $sql = "DELETE FROM $table WHERE ".$this->ToSql($condition);
        $this->Execute($sql);
        return $this->GetAffectedRows();
    }

    public function Count($table, $condition = null, $join = null)
    {
        return $this->SelectScalar($table, 'COUNT(*) AS Count', $condition, $join);
    }

    public function Truncate($table)
    {
        Log::LogQueryType('TRUNCATE', $table);
        $this->AddQuotes($table);
        return $this->Execute("TRUNCATE TABLE $table");
    }

    public function Close()
    {
        $this->driver->close();
        self::Remove($this->name);
    }

    public function ToSql($sqlquery)
    {
        $sql = $sqlquery->sql;
        foreach($sqlquery->params as $key => $param)
        {
            $value = $this->FormatValue($param['type'], $param['value'], $param['list_item_type']);
            $sql = str_replace("@$key", $value, $sql);
        }
        return $sql;
    }

    abstract public function EscapeString($string);

    public function FormatValue($type, $value, $list_item_type = null)
    {
        if (self::IsNull($value)) return 'NULL';

        self::StripType($type);
        switch($type)
        {
            case 'char':
            case 'varchar':
            case 'text':
            case 'tinytext':
            case 'mediumtext':
            case 'longtext':
            case 'date':
            case 'time':
            case 'year':
            case 'datetime':
            case 'timestamp':
            case 'enum':
            case AppConfig::GetInstance()->SQL_STRING:
                $value = $this->EscapeString($value);
                $value = "'$value'";
                break;
            case 'int':
            case 'integer':
            case 'tinyint':
            case 'smallint':
            case 'mediumint':
            case 'bigint':
            case 'float':
            case 'double':
            case 'decimal':
            case AppConfig::GetInstance()->SQL_NUMBER:
                $value = is_numeric($value) ? $value : doubleval($value);
                break;
            case AppConfig::GetInstance()->SQL_BOOLEAN:
                $value = $value ? 1 : 0;
                break;
            case AppConfig::GetInstance()->SQL_LIST:
                if ($list_item_type)
                    foreach ($value as $key => $item)
                        $value[$key] = $this->FormatValue($list_item_type, $item);
                $value = implode(',', $value);
                break;
            case AppConfig::GetInstance()->SQL_STRING_NOQUOTE:
                $value = $this->EscapeString($value);
                break;
        }
        return $value;
    }

    public static function IsNull($value)
    {
        return is_null($value) || (is_string($value) && $value == '#NULL');
    }

    public function AddQuotes(&$list)
    {
        $quote = $this->quote;
        $tmp = is_array($list) ? $list : explode(',', $list);
        foreach($tmp as $t)
        {
            $t = trim($t);
            if (strpos($t, $quote)!==false || strpos($t, '(')!==false || strpos($t, ' ')!==false || $t=='*')
                $output[] = $t;
            else
            {
                $t = str_replace('.', "$quote.$quote", $t);
                $output[] = str_replace("$quote*$quote", '*', $quote.$t.$quote);
            }
        }
        $list = implode(', ', $output);
        return $list;
    }

    protected function GetSelectFields($fields)
    {
        if (is_array($fields))
            $fields = implode(', ', $fields);
        return is_null($fields) ? '*' : $this->AddQuotes($fields);
    }

    protected function GetWhere($condition)
    {
        $where = is_object($condition) ? $this->ToSql($condition) : $condition;
        return $where ? "WHERE $where" : null;
    }

    protected function GetGroupBy($groupby)
    {
        return $groupby ? "GROUP BY $groupby" : null;
    }

    protected function GetOrderBy($orderby)
    {
        if ($orderby)
        {
            $orders = explode(',', $orderby);
            foreach ($orders as $order)
            {
                $order = String::RemoveSpace($order);
                $ex = explode(' ', $order);
                $field = $ex[0];
                $sortby = count($ex) > 1 ? $ex[1] : null;
                if (!String::Contains('(', $field)) $field = "`$field`";
                $out[] = "$field $sortby";
            }
            $orderby = implode(', ', $out);
        }
        return $orderby ? "ORDER BY $orderby" : null;
    }

    protected function GetSelectLimit($limit, &$page, $count)
    {
        if ($limit)
        {
            if ($page < 1) $page = 1;
            if ($count > 0)
            {
                $page_count = ceil($count/$limit);
                if ($page > $page_count) $page = $page_count;
            }
            $offset = ($page - 1) * $limit;
            return "LIMIT $offset, $limit";
        }
    }

    protected function GetCount($count, $groupby, $select, $from)
    {
        if ($count === 0)
        {
            $sql = 'SELECT COUNT(*) AS Count FROM ';
            $sql .= $groupby ? "($select) AS tb" : $from;
            return $this->QueryScalar($sql);
        }
    }

    public function GetForeignKeys($table)
    {
        return array();
    }

    public function StartTransaction($transaction_id)
    {
        if (!$this->transactionId)
        {
            $this->transactionId = $transaction_id;
            return $this->Execute("BEGIN TRANSACTION -- $transaction_id");
        }
    }

    public function CommitTransaction($transaction_id)
    {
        if ($transaction_id == $this->transactionId)
        {
            $this->transactionId = null;
            return $this->Execute("COMMIT -- $transaction_id");
        }
    }

    public static function StripType(&$type)
    {
        $pos = stripos($type, '(');
        if ($pos !== false)
            $type = substr($type, 0, $pos);
    }
}
?>